﻿using System;
using System.ServiceProcess;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.Win32;

namespace SPNotesFor.Administration
{
	/// <summary>
	/// Helper class to register a new extension in the WSS Search Services and in Office Search Server.
	/// </summary>
	sealed class SearchExtender
	{
		private Guid localServerId;
		private String classID;
		private StringBuilder result;


		public SearchExtender(StringBuilder output)
		{
			this.result = output;
			localServerId = SPServer.Local.Id;
		}


		public void ApplyOnWebFront(SPServer server)
		{
			// Submit a job now to deploy the icon on the server and patch docicon.xml
			if (!String.IsNullOrEmpty(this.Icon))
			{
				if (server.Farm.TimerService.Instances.Count < 1)
					throw new Microsoft.SharePoint.SPException("Could not run job. Timer service not found.");

				string name = UpdateDocIconJob.GetName(this.Extension);
				foreach (SPJobDefinition job in server.Farm.TimerService.JobDefinitions)
				{
					if (job.Name == name)
						job.Delete();
				}

				SPOneTimeSchedule schedule = new SPOneTimeSchedule(DateTime.Now.AddHours(-2));
				UpdateDocIconJob jobIcon = new UpdateDocIconJob(Extension, server.Farm.TimerService, server, Icon, IconData);
				jobIcon.Schedule = schedule;
				jobIcon.Update();

				if (localServerId == server.Id) result.AppendLine("Job created.");
				else result.AppendFormat("Job created on {0}.\n", server.Address);
			}
		}

		public void ApplyOnIndexingServer(SPServer server, RegistryKey classesRoot)
		{
			RegistryKey localMachine;
			if (localServerId == server.Id)
			{
				result.AppendLine("Applying changes on local server...");
				localMachine = Registry.LocalMachine;
			}
			else
			{
				result.AppendFormat("Applying changes on server: {0}...\n", server.Address);
				localMachine = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, server.Address);
			}

			if (!DoesFilterInstalled(classesRoot))
			{
				string message;
				if (this.FallbackFilter != null)
				{
					message = String.Format("Skipped because {0} nor {1} is not installed.", this.Filter.Name, this.FallbackFilter.Name);
				}
				else
				{
					message = String.Format("Skipped because {0} is not installed.", this.Filter.Name);
				}

				SPLog.LogString(TraceSeverity.High, "Machine \"{0}\": {1}", server.Address, message);
				result.AppendLine(message).AppendLine();
				return;
			}

			bool hasChanges;
			if ((hasChanges = PatchWSSearchRegistry(localMachine, server.Address)))
				result.AppendLine("WSS Search Server updated.");

			// If Microsoft Search Services Express (MSSX) is installed, we need to perform some additional steps.
			if ((hasChanges |= PatchOSearchRegistry(localMachine, server.Address)))
				result.AppendLine("Office Search Server updated.");

			if (!hasChanges)
				result.AppendLine("No changes.");
		}

		// Office Search Server

		#region PatchOSearchRegistry

		/// <summary>
		/// If MSSX is installed, we will register the extension to the Search Service Provider using the Web Service
		/// (to avoid a dependency to Microsoft.Office.Server.Search.dll).
		/// </summary>
		private bool PatchOSearchRegistry(RegistryKey localMachine, String machineName)
		{
			bool hasChanges;

			// Navigate to  "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office Server\12.0
			// \Search\Applications\<Site>\Gather\Portal_Content\Extensions\ExtensionList"
			using (RegistryKey searchServerKey = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Office Server\12.0\Search", true))
			{
				if (searchServerKey == null)
				{
					result.AppendLine("Microsoft Office Search Server is not installed, no action taken.");
					return false;
				}

				RegistryKey key;
				using (key = searchServerKey.OpenSubKey("Applications"))
				{
					// As it stands in the above comment, we need to resolve the <GUID> value.
					string[] subKeys = key.GetSubKeyNames();
					if (subKeys.Length == 0) return false;

					using (RegistryKey extKey = key.OpenSubKey(subKeys[0] + @"\Gather\Portal_Content\Extensions\ExtensionList", true))
					{
						hasChanges = PatchExtensionList(extKey);
					}
				}

				using (key = searchServerKey.CreateSubKey(@"Setup\ContentIndexCommon\Filters\Extension\." + this.Extension))
				{
					hasChanges |= EnsureKeyValue(key, String.Empty, this.classID, RegistryValueKind.MultiString);
				}

				using (key = searchServerKey.CreateSubKey(@"Setup\Filters\." + this.Extension))
				{
					hasChanges |= EnsureKeyValue(key, "Extension", this.Extension, RegistryValueKind.String);
					hasChanges |= EnsureKeyValue(key, "FileTypeBucket", "1", RegistryValueKind.DWord);
					hasChanges |= EnsureKeyValue(key, "MimeTypes", this.MimeTypes, RegistryValueKind.String);
				}
			}


			// If any changes occured, we need to restart the Office Search Service
			if (hasChanges)
			{
				RestartWindowsService("OSearch", machineName);
			}

			return hasChanges;
		}

		#endregion

		// WSS Search

		#region PatchWSSearchRegistry

		private bool PatchWSSearchRegistry(RegistryKey localMachine, String machineName)
		{
			// Follow what is stated here: http://support.microsoft.com/kb/927675

			bool hasChanges;

			RegistryKey key;
			using (RegistryKey searchKey = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\12.0\Search", true))
			{
				using (key = searchKey.CreateSubKey(@"Setup\ContentIndexCommon\Filters\Extension\." + this.Extension))
				{
					hasChanges = EnsureKeyValue(key, String.Empty, this.classID, RegistryValueKind.MultiString);
				}

				using (key = searchKey.CreateSubKey(@"Setup\Filters\.pdf"))
				{
					hasChanges |= EnsureKeyValue(key, "Extension", this.Extension, RegistryValueKind.String);
					hasChanges |= EnsureKeyValue(key, "FileTypeBucket", "1", RegistryValueKind.DWord);
					hasChanges |= EnsureKeyValue(key, "MimeTypes", this.MimeTypes, RegistryValueKind.String);
				}


				// Navigate to  "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\12.0
				// \Search\Applications\<Site>\Gather\Search\Extensions\ExtensionList"
				using (key = searchKey.OpenSubKey("Applications", true))
				{
					// As it stands in the above comment, we need to resolve the <Site> value.
					string[] subKeys = key.GetSubKeyNames();
					if (subKeys.Length > 0)
					{
						using (RegistryKey extKey = key.OpenSubKey(subKeys[0] + @"\Gather\Search\Extensions\ExtensionList", true))
						{
							if (extKey != null)
								hasChanges |= PatchExtensionList(extKey);
						}
					}
				}
			}


			// If any changes are detected, restart the Windows SharePoint Services Search service
			if (hasChanges)
			{
				RestartWindowsService("SPSearch", machineName);
			}

			return hasChanges;
		}

		#endregion

		// Helper methods

		#region EnsureKeyValue

		/// <summary>
		/// Ensures the value of a Registry key.
		/// </summary>
		/// <returns>Returns true if any changes occured.</returns>
		private static bool EnsureKeyValue(RegistryKey key, string name, string value, RegistryValueKind valueKind)
		{
			if (valueKind == RegistryValueKind.MultiString)
			{
				String[] values = key.GetValue(name) as String[];
				if (values == null || (values.Length > 0 && !value.Equals(values[0], StringComparison.InvariantCultureIgnoreCase)))
				{
					key.SetValue(String.Empty, new String[] { value }, RegistryValueKind.MultiString);
					return true;
				}

				return false;
			}
			else
			{
				if (!value.Equals(Convert.ToString(key.GetValue(name)), StringComparison.InvariantCultureIgnoreCase))
				{
					key.SetValue(name, value, valueKind);
					return true;
				}
				return false;
			}
		}

		#endregion

		#region PatchExtensionList

		/// <summary>
		/// Find the first place where to add the extension value in the extension list key.
		/// </summary>
		private bool PatchExtensionList(RegistryKey extensionListKey)
		{
			// If the configuration has not been modified, the 38 value should be free
			object value38 = extensionListKey.GetValue("38");
			if (value38 == null)
				extensionListKey.SetValue("38", this.Extension, RegistryValueKind.String);
			else
			{
				// Already processed
				if (this.Extension.Equals(value38)) return false;

				// Else we need to find the more convenient free value
				String[] values = extensionListKey.GetValueNames();
				extensionListKey.SetValue((Convert.ToInt32(values[values.Length - 1]) + 1).ToString(),
					this.Extension, RegistryValueKind.String);
			}

			return true;
		}

		#endregion

		#region RestartWindowsService

		private void RestartWindowsService(String serviceName, String machineName)
		{
			ServiceController svcController = new ServiceController(serviceName, machineName);
			if (svcController.Status == ServiceControllerStatus.Running)
			{
				result.AppendFormat("Restarting Service {0}...", svcController.DisplayName).AppendLine();

				svcController.Stop();
				svcController.WaitForStatus(ServiceControllerStatus.Stopped);
			}
			svcController.Start();
		}

		#endregion

		#region DoesFilterInstalled

		/// <summary>
		/// Make sure the product iFilter is installed.
		/// </summary>
		private bool DoesFilterInstalled(RegistryKey classesRoot)
		{
			using (RegistryKey key = classesRoot.OpenSubKey(@"CLSID\" + this.Filter.ClassID))
			{
				classID = this.Filter.ClassID;
				if (key != null) return true;
			}

			if (this.FallbackFilter != null)
			{
				using (RegistryKey key = classesRoot.OpenSubKey(@"CLSID\" + this.FallbackFilter.ClassID))
				{
					classID = this.FallbackFilter.ClassID;
					if (key != null) return true;
				}
			}

			return false;
		}

		#endregion


		/// <summary>
		/// Gets whether there is some servers that does not have the installed.
		/// </summary>
		public bool HasUninstalledFilter { get; private set; }

		public string Extension { get; set; }
		public string MimeTypes { get; set; }
		public ProductFilter Filter { get; set; }
		public ProductFilter FallbackFilter { get; set; }
		public string Icon { get; set; }
		public byte[] IconData { get; set; }
	}
}
